#include <fmt/format.h>
#include <fmt/chrono.h>
#include <fmt/color.h>

#include <filesystem>
#include <iostream>
#include <chrono>

#include "util/logger.h"

using namespace kawauso;
using namespace std;

#define LOG_DIR "logs"
#define LOG_FILE_ID_KEY "log_file_id"

Logger::Logger(const LoggerOptions &options)
    : options(options)
{
    if (options.enableFileStorage) {

        filesystem::path directory(LOG_DIR);
        if (!filesystem::exists(directory)) {
            filesystem::create_directories(directory);
        }

        if (!LocalStore::global.exists(LOG_FILE_ID_KEY)) {
            LocalStore::global.set<uint32_t>(LOG_FILE_ID_KEY, 0);
        }

        this->fileId = LocalStore::global.get<uint32_t>(LOG_FILE_ID_KEY);

        openNewLogFile();
    }
}

Logger::~Logger() {
    if (options.enableFileStorage && fileStream != nullptr) {
        closeLogFile();
    }
}

void Logger::output(LogLevel level, const std::string &message) {

    auto now = chrono::system_clock::now();
    auto seconds = chrono::system_clock::to_time_t(now);
    auto milliseconds = duration_cast<chrono::milliseconds>(now.time_since_epoch()).count() % 1000;

    const static vector<string> LOG_LEVEL_NAMES {
            fmt::format(fmt::fg(static_cast<fmt::color>(0x5F953B)) | fmt::emphasis::bold, "INFO"),
            fmt::format(fmt::fg(static_cast<fmt::color>(0xA3872B)) | fmt::emphasis::bold, "WARN"),
            fmt::format(fmt::fg(static_cast<fmt::color>(0xED5653)) | fmt::emphasis::bold, "ERROR")
    };

    string fullMessage = fmt::format("[{:%Y-%m-%d %H:%M:%S}.{:03}] {} => {}\n",
                                     fmt::localtime(seconds),
                                     milliseconds,
                                     LOG_LEVEL_NAMES[int(level)],
                                     message);

    if (options.enableFileStorage) {
        fileMutex.lock();
        if (fputs_unlocked(fullMessage.c_str(), fileStream) != EOF) {
            fileSize += fullMessage.size();
            if (fileSize >= options.maxFileSize) {
                LocalStore::global.set(LOG_FILE_ID_KEY, ++fileId);
                if (closeLogFile()) {
                    openNewLogFile();
                }
            }
        } else {
            options.enableFileStorage = false;
            output(LogLevel::ERROR, fmt::format(
                    "Failed to write LogFile, reason: {}", strerror(errno)));
        }
        fileMutex.unlock();
    }

    std::cout << fullMessage;
}

void Logger::openNewLogFile() {

    string filename = fmt::format("logs/log-{}", fileId);
    fileStream = fopen(filename.c_str(), "ab");

    if (fileStream != nullptr) {
        this->fileSize = ftell(fileStream);
    } else {
        options.enableFileStorage = false;
        output(LogLevel::ERROR, fmt::format(
                "Failed to open new LogFile, reason: {}", strerror(errno)));
    }
}

bool Logger::closeLogFile() {
    if (fclose(fileStream) == EOF) {
        options.enableFileStorage = false;
        output(LogLevel::ERROR, fmt::format(
                "Failed to close LogFile, reason: {}", strerror(errno)));
        return false;
    }
    return true;
}
